package gwtool.backend;

import gwtool.container.Benutzter;
import gwtool.container.DataSet;
import gwtool.container.DataSetItem;
import gwtool.container.Gruppe;
import gwtool.gui.MainController;
import gwtool.gui.components.MyProgressbar;

import java.awt.Dialog.ModalityType;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class Comm {

	private String adresse = "https://alarmapp.chrku.de/gwtool/clientapi/";

	private File loginCacheFile = new File(System.getProperty("user.home") + "/gwtool/loginhash.sec");

	private DataSetController dataSetController;

	public Comm(DataSetController dataSetController) {

		this.dataSetController = dataSetController;
		try {
			enableOwnTrustManager();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void startOnlineUpdate(final boolean exitOnEnd) {

		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {

				if (istServerErreichbar()) {
					final MyProgressbar dialog = new MyProgressbar(MainController.getInstance().getJFrame());
					dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
					dialog.setLocationRelativeTo(null);
					dialog.setSize(300, 50);
					dialog.setTitle("Daten zum Server senden");
					dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
					Thread t2 = new Thread(new Runnable() {

						@Override
						public void run() {

							dialog.setVisible(true);
						}
					});
					t2.start();
					try {
						updateCache();
						dialog.updateProgressBar(30);
						boolean commitOK = commitItems();
						dialog.updateProgressBar(60);
						if (commitOK) {
							dataSetController.removeDataSetsWithChanges();
						}
						updateCache();
						dialog.updateProgressBar(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
					dialog.dispose();
					dataSetController.speichereLocal();
					MainController.getInstance().updateGui();
					if (exitOnEnd) {
						System.exit(0);
					}
				} else {
					if (!MainController.getInstance().isStartMode()) {
						dataSetController.speichereLocal();
						JOptionPane.showMessageDialog(MainController.getInstance().getJFrame(),
								"<html>Server nicht erreichbar!<br>"
										+ "Die Daten werden Local gespeichert und beim nächsten "
										+ "Programmstart zum Server übertragen!");
					}
				}
				MainController.getInstance().setStartMode(false);
			}
		});
		t.start();
	}

	public boolean commitItems() {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		Document document;
		Marshaller marDataSetItem;
		String finalAnfrage = "";
		try {
			marDataSetItem = JAXBContext.newInstance(DataSetItem.class).createMarshaller();
			marDataSetItem.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			marDataSetItem.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
			document = factory.newDocumentBuilder().newDocument();
			Element topElement = document.createElement("CommitAnfrage");
			document.appendChild(topElement);
			createLoginInfo(dataSetController.getBenutzter(), topElement, document);
			Element dataSets = document.createElement("dataSets");
			topElement.appendChild(dataSets);
			for (DataSet dataSet : dataSetController.getDataSetList()) {
				if (dataSet.isChanged()) {
					Element dataSetElement = document.createElement("dataSet");
					dataSetElement.setAttribute("id", dataSet.getId() + "");
					dataSetElement.setAttribute("version", dataSet.getVersion() + "");
					dataSetElement.setAttribute("onlineversion", dataSet.getOnlineVersion() + "");
					dataSetElement.setAttribute("gruppe", dataSet.getGruppe() + "");
					dataSetElement.setAttribute("type", dataSet.getType());
					dataSetElement.setAttribute("del", dataSet.isDeletedInt() + "");
					for (DataSetItem item : dataSet.getItemList()) {
						if (!item.isLocalErstellt()) {
							continue;
						}
						Element dataSetItemElement = document.createElement("datasetitem");
						dataSetItemElement.setAttribute("id", item.getId() + "");
						Date dt = new Date(item.getTimestamp());
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
						String currentTime = sdf.format(dt);
						dataSetItemElement.setAttribute("timestamp", currentTime);
						String data = "";
						StringWriter writer = new StringWriter();
						marDataSetItem.marshal(item, writer);
						data = writer.getBuffer().toString();
						dataSetItemElement.setAttribute("data", data);
						dataSetElement.appendChild(dataSetItemElement);
					}
					dataSets.appendChild(dataSetElement);
				}
			}
			// jetzt noch in einen String Transformieren.
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
			transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
			DOMSource source = new DOMSource(document);
			StreamResult result = new StreamResult(new StringWriter());
			transformer.transform(source, result);
			finalAnfrage = streamResultToString(result);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			String ans = sentRequest("add.php", finalAnfrage);
			Scanner scanner = new Scanner(ans);
			scanner.useDelimiter("\\n");
			StringBuilder b = new StringBuilder();
			while (scanner.hasNext()) {
				String s = scanner.next();
				b.append(s);
			}
			System.out.println(b.toString());
			// parseUpdateRequest(b.toString(), true);
			scanner.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	public boolean updateCache() {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		Document document;
		String finalAnfrage = "";
		try {
			document = factory.newDocumentBuilder().newDocument();
			Element topElement = document.createElement("Updateanfrage");
			document.appendChild(topElement);
			createLoginInfo(dataSetController.getBenutzter(), topElement, document);
			Element dataSets = document.createElement("DataSets");
			topElement.appendChild(dataSets);
			for (DataSet dataSet : dataSetController.getDataSetList()) {
				if (dataSet.getId() != -1) {
					Element dataSetElement = document.createElement("DataSet");
					dataSetElement.setAttribute("id", dataSet.getId() + "");
					dataSetElement.setAttribute("onlineversion", dataSet.getOnlineVersion() + "");
					dataSets.appendChild(dataSetElement);
				}
			}
			// jetzt noch in einen String Transformieren.
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
			transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
			DOMSource source = new DOMSource(document);
			StreamResult result = new StreamResult(new StringWriter());
			transformer.transform(source, result);
			finalAnfrage = streamResultToString(result);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			String ans = sentRequest("update.php", finalAnfrage);
			Scanner scanner = new Scanner(ans);
			scanner.useDelimiter("\\n");
			StringBuilder b = new StringBuilder();
			while (scanner.hasNext()) {
				String s = scanner.next();
				b.append(s);
			}
			parseUpdateRequest(b.toString());
			scanner.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	private void parseUpdateRequest(String ans) {

		System.out.println(ans);
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		Document document;
		Unmarshaller unmarGereat;
		try {
			unmarGereat = JAXBContext.newInstance(DataSetItem.class).createUnmarshaller();
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.parse(stringToInputSource(ans));
			// Gruppen einlesen
			dataSetController.getBenutzter().getGruppen().clear();
			NodeList gruppenList = document.getElementsByTagName("gruppe");
			for (int l = 0; l < gruppenList.getLength(); l++) {
				int id = Integer.parseInt(gruppenList.item(l).getAttributes().getNamedItem("id").getNodeValue());
				String name = gruppenList.item(l).getAttributes().getNamedItem("name").getNodeValue();
				int read = Integer.parseInt(gruppenList.item(l).getAttributes().getNamedItem("read").getNodeValue());
				int write = Integer.parseInt(gruppenList.item(l).getAttributes().getNamedItem("write").getNodeValue());
				int del = Integer.parseInt(gruppenList.item(l).getAttributes().getNamedItem("del").getNodeValue());
				if (del == 0) { // gelöschte gruppen ausblenden
					Gruppe neueGruppe = new Gruppe(id, name, read, write);
					dataSetController.getBenutzter().getGruppen().add(neueGruppe);
				}
			}
			// Datasets einlesen
			NodeList datasetList = document.getElementsByTagName("dataset");
			for (int l = 0; l < datasetList.getLength(); l++) {
				int id = Integer.parseInt(datasetList.item(l).getAttributes().getNamedItem("id").getNodeValue());
				int onlineVersion = Integer.parseInt(datasetList.item(l).getAttributes().getNamedItem("version")
						.getNodeValue());
				int gruppe = Integer.parseInt(datasetList.item(l).getAttributes().getNamedItem("gruppe").getNodeValue());
				// Vorhandene überspringen
				// boolean skipEintrag = false;
				// for(DataSet set :dataSetController.getDataSetList()) {
				// if(set.getId() == id && id != -1) {
				// if(set.getVersion() == onlineVersion) {
				// skipEintrag = true;
				// break;
				// }
				// }
				// }
				// if (skipEintrag) {
				// System.out.println("Sollte nicht vorkommen"+ id);
				// continue;
				// }
				NodeList dataItemNodeList = datasetList.item(l).getChildNodes();
				List<DataSetItem> list = new ArrayList<>();
				for (int i = 0; i < dataItemNodeList.getLength(); i++) {
					if (dataItemNodeList.item(i).getNodeName().equals("datasetitem")) {
						DataSetItem setItem = null;
						// Gereat
						setItem = (DataSetItem) unmarGereat.unmarshal(new StreamSource(new StringReader(dataItemNodeList
								.item(i).getAttributes().getNamedItem("data").getNodeValue())));
						int setId = Integer.parseInt(dataItemNodeList.item(i).getAttributes().getNamedItem("id")
								.getNodeValue());
						setItem.setId(setId);
						setItem.setLocalErstellt(false);
						list.add(setItem);
					}
				}
				// Normaler Modus
				DataSet localSet = dataSetController.getEintragByDataSetId(id);
				DataSet newDataSet = new DataSet(id, onlineVersion, gruppe, list);
				if (localSet == null) {
					dataSetController.getDataSetList().add(newDataSet);
				} else if (localSet.getOnlineVersion() != newDataSet.getVersion()) {
					if (localSet.isChanged()) {
						DataSet conflicktfree = MainController.handelConflickt(localSet, newDataSet);
						conflicktfree.setChanged(true);
						dataSetController.getDataSetList().remove(localSet);
						dataSetController.getDataSetList().add(conflicktfree);
					} else {
						dataSetController.getDataSetList().remove(localSet);
						dataSetController.getDataSetList().add(newDataSet);
						System.out.println("merge " + newDataSet.getAktuellesDataItem().getBezeichner());
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String sentRequest(String page, String body) throws Exception {

		URL url = new URL(adresse + page);
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		connection.setRequestMethod("POST");
		connection.setDoInput(true);
		connection.setDoOutput(true);
		connection.setUseCaches(false);
		connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		connection.setRequestProperty("Content-Length", String.valueOf(body.length()));
		OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
		String question = "data=" + URLEncoder.encode(body, "UTF-8");
		writer.write(question);
		writer.flush();
		BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
		StringBuilder b = new StringBuilder();
		for (String line; (line = reader.readLine()) != null;) {
			b.append(line);
		}
		writer.close();
		reader.close();
		return b.toString();
	}

	public void createLoginInfo(Benutzter nutzer, Element topElement, Document document) {

		Element login = document.createElement("Login");
		login.setAttribute("email", nutzer.getEMail());
		login.setAttribute("pass", nutzer.getPassWord());
		topElement.appendChild(login);
	}

	private String streamResultToString(StreamResult result) {

		StringWriter sw = (StringWriter) result.getWriter();
		StringBuffer sb = sw.getBuffer();
		return sb.toString();
	}

	private InputSource stringToInputSource(String ans) {

		StringReader reader = new StringReader(ans);
		InputSource inSource = new InputSource(reader);
		return inSource;
	}

	/**
	 * @throws Exception
	 */
	private void enableOwnTrustManager() throws Exception {

		// Create a trust manager that does not validate certificate chains
		TrustManager[] trustAllCerts = new TrustManager[] { new OwnTrustManager() };
		// Install the all-trusting trust manager
		try {
			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
		} catch (Exception e) {
			//
		}
	}

	public boolean isUserInfoOK(Benutzter user) {

		if (istServerErreichbar()) {
			try {
				String response = sentRequest("logincheck.php", user.getEMail() + "|***|" + user.getPassWord());
				if (response.equals("OK!")) {
					userInfoLocalAblegen(user);
					return true;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			if (isOfflineLoginOK(user)) {
				JOptionPane.showMessageDialog(null, "Im Offlinemode eingelogged");
				return true;
			}
		}
		return false;
	}

	private void userInfoLocalAblegen(Benutzter user) {

		if (!loginCacheFile.getParentFile().exists()) {
			loginCacheFile.getParentFile().mkdirs();
		}
		if (!loginCacheFile.exists()) {
			try {
				loginCacheFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			FileWriter writer = new FileWriter(loginCacheFile);
			writer.write(user.getEMail() + "|***|" + CryptoController.hashPWSHA(user.getPassWord() + user.getEMail()));
			writer.flush();
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean isOfflineLoginOK(Benutzter user) {

		File f = new File(System.getProperty("user.home") + "/gwtool/loginhash.sec");
		if (f.exists()) {
			try {
				Scanner scanner = new Scanner(f);
				scanner.useDelimiter("\\|\\*\\*\\*\\|");
				String nameFromFile = scanner.next();
				if (user.getEMail().equals(nameFromFile)) {
					String hash = CryptoController.hashPWSHA(user.getPassWord() + user.getEMail());
					String fileHash = scanner.next();
					if (hash.equals(fileHash)) {
						scanner.close();
						return true;
					}
				}
				scanner.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return false;
	}

	public void clearLoginCache() {

		loginCacheFile.delete();
	}

	public boolean istServerErreichbar() {

		try {
			String ans = sentRequest("index.php", "");
			if (ans.startsWith("Connection OK! xmlversion=")) {
				int version = Integer.parseInt(ans.substring("Connection OK! xmlversion=".length()));
				if (version > Integer.parseInt(MainController.getXmlversion())) {
					JOptionPane.showMessageDialog(null, "Das Programm ist veraltet, es ist ein Update erforderlich.");
					System.exit(0);
				}
				return true;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
}
